/**
 * Created Time: 2022.10.27
 * File name:    file_and_string.h
 * Author:       zgy(zhangguangyan@wicri.org)
 * Brief:        一些文件读取和字符串的工具
 * Include:      GetFiles(), StringSplit(), LineClean()
 */

#ifndef UTILS_FILE_AND_STRING_H
#define UTILS_FILE_AND_STRING_H

//* sys */
#if defined(WIN32) || defined(_WIN64) /* On Windows */
#include <io.h>
#else /* On Linux */
#include <dirent.h>
#endif
#include <unistd.h>

#include <cstring>
#include <iostream>
#include <string>
#include <vector>

/** 获取目录下全部文件名
 * path: 一个文件夹路径
 * @return
 * 若目录不为空，返回一个包含所有文件名的vector。
 * 若目录为空，返回空vector。
 */
inline auto GetFiles(std::string path) -> std::vector<std::string> {
    std::cout << "寻找所有路径文件" << std::endl;
    // 文件句柄
    std::vector<std::string> files;
#if defined(WIN32) || defined(_WIN64) // On Windows
    // intptr_t       hFile = 0;
    // 文件信息，声明一个存储文件信息的结构体
    struct _finddata_t fileinfo;
    std::string        p;                                                            // 字符串，存放路径
    if ((hFile = _findfirst(p.assign(path).append("\\*").c_str(), &fileinfo)) != -1) // 若查找成功，则进入
    {
        do {
            // 如果是目录,迭代之（即文件夹内还有文件夹）
            if ((fileinfo.attrib & _A_SUBDIR)) {
                // 忽略文件名"."和文件名".."
                // "."表示当前目录
                // ".."表示当前目录的父目录
                // 判断时，两者都要忽略，不然就无限递归跳不出去了！
                if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
                    files = GetFiles(p.assign(path).append("\\").append(fileinfo.name));
            }
            // 如果不是,加入列表
            else { files.push_back(p.assign(path).append("\\").append(fileinfo.name)); }
        } while (_findnext(hFile, &fileinfo) == 0);
        //_findclose函数结束查找
        _findclose(hFile);
    }
#else // On Linux
    DIR*           pDir; //  是头文件<dirent.h>的类型
    struct dirent* ptr;  // opendir、readdir这些都是头文件dirent.h
    if (!(pDir = opendir(path.c_str()))) {
        std::vector<std::string> nopath;
        nopath.clear();
        std::cout << "没找到路径，给出的文件目录是错的吗？" << std::endl;
        return nopath;
    }
    while ((ptr = readdir(pDir)) != 0) {
        // strcmp是C语言里的，只导入string,然后std::strcmp都是没有的
        if (std::strcmp(ptr->d_name, ".") != 0 && std::strcmp(ptr->d_name, "..") != 0) { // ignore . or .. file
            std::cout << ptr->d_name << std::endl;
            files.push_back(path + "/" + ptr->d_name); // 可以只保留名字
        }
    }
    closedir(pDir);
#endif
    std::cout << "找到路径文件" << std::endl;
    return files;
}

/** 使用字符串分割字符串
 * str: 一个字符串
 * splits: 分隔符
 * @return
 * 若字符串中存在分隔符，则返回分割后（不包含分隔符的）一个字符串vector。
 * 若字符串中不存在分隔符，则返回一个包含原字符串的vector。
 */
inline auto StringSplit(const std::string& str, const std::string& splits) -> std::vector<std::string> {
    std::vector<std::string> results;
    if (str == "") {
        std::vector<std::string> nostr;
        nostr.clear();
        return nostr;
    }
    // 在字符串末尾也加入分隔符，方便截取最后一段
    std::string strs = str + splits;
    size_t      pos  = strs.find(splits);
    int         step = splits.size();

    // 若找不到内容则字符串搜索函数返回 npos
    while (pos != strs.npos) {
        std::string temp = strs.substr(0, pos);
        results.push_back(temp);
        // 去掉已分割的字符串,在剩下的字符串中进行分割
        strs = strs.substr(pos + step, strs.size());
        pos  = strs.find(splits);
    }
    return results;
}

/** 处理注释，空格，和空行的函数
 * line，表示一行文本内容
 * comment_str，表示注释前导字符串，默认设置为#，也可以用//或者%
 * string删除字符串，http://www.cplusplus.com/reference/string/string/erase/
 * string查找字符串，http://www.cplusplus.com/reference/string/string/find_first_not_of/
 * @return
 * 如果行首有多个空格或者制表符交错，行尾为注释，那么处理后字符串line行首多个空格(和tab)和行尾注释被删掉，只保留有意义的内容。
 * 如果这一行只有空格，制表符，注释，那么处理后line为空;
 */
inline auto LineClean(std::string& line, const std::string comment_str = "#") -> std::string {
    for (char& c : line) // C++11以上版本的语法
    {
        // 制表符 tab，逗号，分号都当作有效的分隔符，统一转成空格
        // 为了避免错误，回车符和换行符也转为空格（否则无法处理空行）
        if (c == '\t' || c == ',' || c == ';' || c == '\r' || c == '\n') c = ' ';
    }

    /* 调用的string& erase (size_t pos = 0, size_t len = npos), len为默认参数 */
    line.erase(0, line.find_first_not_of(" ")); // 删除行首空格
    line.erase(line.find_last_not_of(" ") + 1); // 删除行末空格


    /* 查找注释符所在位置，如果不存在，则得到string::npos */
    int n_comment_start = line.find_first_of(comment_str);
    if (n_comment_start != std::string::npos) // 这一句必须的
        line.erase(n_comment_start);          // 删除注释
    return line;
}

#include <sstream>

inline auto IntToString(int i) -> std::string {
    std::stringstream ss;
    ss << i;
    std::string i_string;
    ss >> i_string;
    return i_string;
}

template <typename T>
std::string to_string_with_precision(const T a_value, const int n = 6) {
    std::ostringstream out;
    out.precision(n);
    out << std::fixed << a_value;
    return out.str();
}

#endif // UTILS_FILE_AND_STRING_H